package nl.utwente.ewi.hmi.multitouch.drivers.tuio;

import java.awt.Dimension;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import com.merl.diamondtouch.DtlibSegment;

import nl.utwente.ewi.hmi.multitouch.SimpleTangible;
import nl.utwente.ewi.hmi.multitouch.SurfaceType;
import nl.utwente.ewi.hmi.multitouch.Tangible;
import nl.utwente.ewi.hmi.multitouch.analysis.Segment;
import nl.utwente.ewi.hmi.multitouch.io.TouchStream;

import TUIO.TuioClient;
import TUIO.TuioCursor;
import TUIO.TuioListener;
import TUIO.TuioObject;
import TUIO.TuioPoint;
import TUIO.TuioTime;

public class TuioTouchStream extends TouchStream {

	private TuioClient client = null;
	private boolean isOpen = false;
	
	private Map<Long, Tangible> tangibleHistory = null;
	
	private Object tuioActor;
	
	private Set<Object> actors;

	private final int width;
	private final int height;
	
	private TuioPointMaintainer points = null;
	
	public TuioTouchStream(TuioTouchDeviceInfo touchDeviceInfo) {
		super(touchDeviceInfo);

		this.width = touchDeviceInfo.getSize().width;
		this.height = touchDeviceInfo.getSize().height;

		tuioActor = new Object();
		actors = Collections.unmodifiableSet(new HashSet<Object>(Arrays.asList(tuioActor)));

		tangibleHistory = new HashMap<Long, Tangible>();

		this.client = new TuioClient(touchDeviceInfo.getPort());
		this.points = new TuioPointMaintainer();
		
		this.client.addTuioListener(this.points);

	}

	@Override
	public boolean close() throws IOException {
		if(isOpen) {
			client.disconnect();
			isOpen = false;
			return true;
		}

		return false;
	}

	@Override
	public Set<Object> getActors() {
		return actors;
	}
	
	/*
	@Override
	public Frame[] getFrames() throws IOException {
		if(!points.waitForRefresh()) {
			return this.frames;
		}
		
		Tangible[] tangibles = this.frame.getTangibleMap();
		TouchType[] touchTypes = this.frame.getTouchTypeMap();
		
		Arrays.fill(tangibles, null);
		Arrays.fill(touchTypes, TouchType.NEGATIVE);
		
		synchronized(points) {
		
			for(Entry<Long, TuioPoint> entry : points.points.entrySet()) {
				
				TuioPoint point = entry.getValue();
				Long id = entry.getKey();
				
				Tangible tangible = tangibleHistory.get(id);

				if(tangible == null) {
					tangible = new TuioTangible(this.tuioActor, id);
					tangibleHistory.put(id, tangible);
				}
				
				int x = point.getScreenX(width);
				int y = point.getScreenY(height);

				if(!(x < width && y < height && x >= 0 && y >= 0)) {
					continue;
				}

				tangibles[x + y * width] = tangible;
				touchTypes[x + y * width] = TouchType.POSITIVE;
			}
		}
		
		// redraw frames
		
		
		
		return this.frames;
	}

*/
	@Override
	public boolean isOpen() {
		return this.isOpen;
	}

	@Override
	public void read(Set<Segment> segments) throws IOException {

		long time = System.currentTimeMillis();
		
		if(!points.waitForRefresh()) {
			return;
		}

		synchronized(points) {
			
			for(Entry<Long, TuioPoint> entry : points.points.entrySet()) {
				
				TuioPoint point = entry.getValue();
				Long id = entry.getKey();
				
				Tangible tangible = tangibleHistory.get(id);

				if(tangible == null) {
					tangible = new TuioTangible(this.tuioActor, id);
					tangibleHistory.put(id, tangible);
				}
				
				int x = point.getScreenX(width);
				int y = point.getScreenY(height);

				if(!(x < width && y < height && x >= 0 && y >= 0)) {
					continue;
				}

				segments.add(new TuioSegment(x, y, tangible, time));
			}
		}
	}

	@Override
	public boolean open() throws IOException {
		if(!isOpen) {
			client.connect();
			isOpen = true;
			return true;
		}
			
		return false;
	}
	
	private static class TuioPointMaintainer implements TuioListener {

		Map<Long, TuioPoint> points = new HashMap<Long, TuioPoint>();

		@Override
		public synchronized void addTuioCursor(TuioCursor tuioCursor) {
			if(points.containsKey(tuioCursor.getSessionID())) {
				return;
			}
			points.put(tuioCursor.getSessionID(), tuioCursor);
		}

		@Override
		public synchronized void addTuioObject(TuioObject tuioObject) {
			if(points.containsKey(tuioObject.getSessionID())) {
				return;
			}
			points.put(tuioObject.getSessionID(), tuioObject);
		}

		@Override
		public void refresh(TuioTime time) {
			synchronized(this) {
				this.notify();
			}
		}

		public boolean waitForRefresh() {
			synchronized(this) {
				try {
					this.wait();
				} catch (InterruptedException e) {
					return false;
				}
			}
			return true;
		}
		
		@Override
		public synchronized void removeTuioCursor(TuioCursor tuioCursor) {
			points.remove(tuioCursor.getSessionID());
			
		}

		@Override
		public synchronized void removeTuioObject(TuioObject tuioObject) {
			points.remove(tuioObject.getSessionID());
			
		}

		@Override
		public synchronized void updateTuioCursor(TuioCursor tuioCursor) {
			this.addTuioCursor(tuioCursor);
		}

		@Override
		public synchronized void updateTuioObject(TuioObject tuioObject) {
			this.addTuioObject(tuioObject);
		}
		
	}

	private static final class TuioSegment implements Segment {

		final Rectangle2D bounds;
		final float mass;
		final Point2D origin;
		final Path2D perimeter;
		final Tangible tangible;
		final long time;

		public TuioSegment(int x, int y, Tangible tangible,  long time) {
			int width = 1;
			int height = 1;

			this.bounds = new Rectangle2D.Float(x -0.5f, y - 0.5f, width, height);

			this.perimeter = new Path2D.Float(this.bounds);
			this.origin = new Point2D.Float((float)this.bounds.getCenterX(), (float)this.bounds.getCenterY());

			this.time = time;
			this.tangible = tangible;
			this.mass = width * height;
		}

		@Override
		public Rectangle2D getBounds() {
			return this.bounds;
		}

		@Override
		public float getMass() {
			return this.mass;
		}

		@Override
		public Point2D getOrigin() {
			return this.origin;
		}

		@Override
		public Path2D getPerimeter() {
			return this.perimeter;
		}

		@Override
		public Tangible getTangible() {
			return this.tangible;
		}

		@Override
		public long getTime() {
			return this.time;
		}

		@Override
		public boolean isAmbiguous() {
			return true;
		}

		@Override
		public int compareTo(Segment o) {
			return (int) (this.getTime() - o.getTime());
		}
		
	}
}
